package com.ideaaedi.commonspring.parser;

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PlaceholderConfigurerSupport;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.SimpleAliasRegistry;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurablePropertyResolver;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * StringValueResolver工具类
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2100.6.5
 */
public class StringValueResolverHelper {
    
    private static final String KEY = "key_";
    
    private final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer;
    
    private StringValueResolverHelper(PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer) {
        this.propertySourcesPlaceholderConfigurer = propertySourcesPlaceholderConfigurer;
    }
    
    /**
     * 初始化上下文
     *
     * @see StringValueResolverHelper#init(Map, ConfigurableConversionService, Config)
     */
    public static StringValueResolverHelper init(@Nullable Map<String, Object> env) {
        return init(env, null, null);
    }
    
    /**
     * 初始化上下文
     *
     * @see StringValueResolverHelper#init(Map, ConfigurableConversionService, Config)
     */
    public static StringValueResolverHelper init(@Nullable Map<String, Object> env, @Nullable Config config) {
        return init(env, null, config);
    }
    
    /**
     * 初始化上下文
     *
     * @see StringValueResolverHelper#init(Map, ConfigurableConversionService, Config)
     */
    public static StringValueResolverHelper init(@Nullable Map<String, Object> env,
                                                 @Nullable ConfigurableConversionService conversionService) {
        return init(env, conversionService, null);
    }
    
    /**
     * 初始化上下文 & 设置类型转换器
     *
     * @param env 上下文信息，用于替换对应的占位符
     * @param conversionService 类型转换器
     * @param config 配置
     *
     * @return 当前实例
     */
    public static StringValueResolverHelper init(@Nullable Map<String, Object> env,
                                                 @Nullable ConfigurableConversionService conversionService,
                                                 @Nullable Config config) {
        Environment environment = new AbstractEnvironment() {
            @Override
            protected void customizePropertySources(@NonNull MutablePropertySources propertySources) {
                if (env != null) {
                    propertySources.addLast(new MapPropertySource("JdEnvironment", env));
                }
            }
            
            @Override
            @NonNull
            protected ConfigurablePropertyResolver createPropertyResolver(@NonNull MutablePropertySources propertySources) {
                PropertySourcesPropertyResolver propertySourcesPropertyResolver = new PropertySourcesPropertyResolver(propertySources);
                if (conversionService != null) {
                    propertySourcesPropertyResolver.setConversionService(conversionService);
                }
                return propertySourcesPropertyResolver;
            }
        };
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer() {
    
            @Override
            protected void processProperties(@NonNull ConfigurableListableBeanFactory beanFactoryToProcess,
                                             @NonNull ConfigurablePropertyResolver propertyResolver) throws BeansException {
                // config
                Config currConfig = config == null ? defaultConfig() : config;
                super.setPlaceholderPrefix(currConfig.getPlaceholderPrefix());
                super.setPlaceholderSuffix(currConfig.getPlaceholderSuffix());
                super.setValueSeparator(currConfig.getValueSeparator());
                super.setIgnoreUnresolvablePlaceholders(currConfig.isIgnoreUnresolvablePlaceholders());
                super.setTrimValues(currConfig.isTrimValues());
                // invoke super
                super.processProperties(beanFactoryToProcess, propertyResolver);
            }
        };
        propertySourcesPlaceholderConfigurer.setEnvironment(environment);
        return new StringValueResolverHelper(propertySourcesPlaceholderConfigurer);
    }
    
    /**
     * 获取默认配置
     *
     * @return  默认配置
     */
    public static Config defaultConfig() {
        return new Config();
    }
    
    /**
     * 解析表达式
     *
     * @param expression 待解析的表达式. 格式形如：${xxx}
     *
     * @return 解析后的表达式
     */
    @Nullable
    public String parse(@Nullable String expression) {
        if (StringUtils.isBlank(expression)) {
            return expression;
        }
        JdDefaultListableBeanFactory beanFactory = new JdDefaultListableBeanFactory();
        Map<String, String> map = new HashMap<>(2);
        map.put(KEY, expression);
        beanFactory.init(map);
        propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
        return beanFactory.getAliasMap().get(KEY);
    }
    
    /**
     * 解析表达式
     *
     * @param exprMap 待解析的表达式map(map的values即为表达式. 表达式格式形如：${xxx})
     *
     * @return 解析后的表达式map
     */
    @NonNull
    public LinkedHashMap<String, String> parse(@Nullable LinkedHashMap<String, String> exprMap) {
        if (CollectionUtils.isEmpty(exprMap)) {
            return new LinkedHashMap<>(1);
        }
        JdDefaultListableBeanFactory beanFactory = new JdDefaultListableBeanFactory();
        Map<String, String> tmpMap = new HashMap<>(16);
        int idx = 0;
        for (String value : exprMap.values()) {
            tmpMap.put(KEY + idx, value);
            idx++;
        }
        beanFactory.init(tmpMap);
        propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
        Map<String, String> aliasMap = beanFactory.getAliasMap();
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>(16);
        String key;
        for (int i = 0; idx > 0; idx--) {
            key = KEY + i;
            linkedHashMap.put(key, aliasMap.get(key));
            i++;
        }
        return linkedHashMap;
    }
    
    /**
     * 解析表达式
     *
     * @param exprList 待解析的表达式集合。 表达式格式形如：${xxx}
     *
     * @return 解析后的表达式list
     */
    @NonNull
    public List<String> parse(@Nullable List<String> exprList) {
        if (CollectionUtils.isEmpty(exprList)) {
            return new ArrayList<>(1);
        }
        JdDefaultListableBeanFactory beanFactory = new JdDefaultListableBeanFactory();
        Map<String, String> tmpMap = new HashMap<>(2);
        int size = exprList.size();
        for (int i = 0; i < size; i++) {
            tmpMap.put(KEY + i, exprList.get(i));
        }
        beanFactory.init(tmpMap);
        propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
        Map<String, String> aliasMap = beanFactory.getAliasMap();
        List<String> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            list.add(aliasMap.get(KEY + i));
        }
        return list;
    }
    
    /**
     * (non-javadoc)
     */
    private static class JdDefaultListableBeanFactory extends DefaultListableBeanFactory {
        
        private static final Field aliasMapField;
        
        static {
            try {
                aliasMapField = SimpleAliasRegistry.class.getDeclaredField("aliasMap");
                aliasMapField.setAccessible(true);
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException(e);
            }
        }
        
        @Getter
        private Map<String, String> aliasMap;
        
        void init(Map<String, String> keyAndExpression) {
            try {
                //noinspection unchecked
                aliasMap = (Map<String, String>) aliasMapField.get(this);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
            aliasMap.putAll(keyAndExpression);
        }
        
    }
    
    /**
     * 配置类
     *
     * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
     * @since 2100.6.7
     */
    @Setter
    @Getter
    public static class Config {
    
        private Config() {
        }
    
        /**
         * @see PlaceholderConfigurerSupport#placeholderPrefix
         */
        private String placeholderPrefix = PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_PREFIX;
    
        /**
         * @see PlaceholderConfigurerSupport#placeholderSuffix
         */
        private String placeholderSuffix = PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_SUFFIX;
    
        /**
         * @see PlaceholderConfigurerSupport#valueSeparator
         */
        private String valueSeparator = PlaceholderConfigurerSupport.DEFAULT_VALUE_SEPARATOR;
    
        /**
         * 是否对解析后的整体结果进行trim（而不是对占位符对应的值trim）
         * @see PlaceholderConfigurerSupport#trimValues
         */
        private boolean trimValues = false;
    
        /**
         * @see PlaceholderConfigurerSupport#ignoreUnresolvablePlaceholders
         */
        private boolean ignoreUnresolvablePlaceholders = false;
    
    }
}
